#include "libpluginaso/combinatory_policy_comp_imp.h"

//!---------Sequential----------
bool SeqCombinatorialGroup::combine(const Avs& elements, CombinationObserver* observer, CombinationStatus& status)
{
    CombGroupIterator it(combAlgs);
    CombArgIterator argit(combArgs);
    CombinationStatus auxStatus;
    status.name = get_name();
    bool continueComb(true);

    while (!it.end() && continueComb)
    {
        Avs intersecAv;             //! elementos disponibles intersección los que debo usar
        set_intersection(elements.begin(), elements.end(), argit->begin(), argit->end(), inserter(intersecAv, intersecAv.begin()));

        continueComb = (*it)->combine(intersecAv, observer, auxStatus);
        if (auxStatus.status == Success)
        {
            status.status = Success;
        }
        ++it;
        ++argit;
    }
    return continueComb;
}

//!----------Parallel------------
//! setea el siguiente observador, al cual tengo que emviarle mi combinacion
void ParallelCombinatory::AuxObserverImp::set_next_obs(AuxObserver* obs)
{
    nextObs = obs;
}

//! Establece el nexo entre los oservadores
bool ParallelCombinatory::AuxObserverImp::combine_obs(const Avs* combination, const Avs* itemsAvailable)
{
    Avs local(*combination);                // hago una copia local
    Avs* const old_lastComb = lastComb;     // genero una copia en el stack
    lastComb = &local;                      // piso el valor anterior

    Avs localItemAv(*itemsAvailable);          // hago una copia local
    Avs* const old_itAvailable = itAvailable;     // genero una copia en el stack
    itAvailable = &localItemAv;                // piso el valor anterior

    Avs intersecAv;                         //! elementos disponibles intersección los que debo usar
    set_intersection(reference_elements.begin(), reference_elements.end(), itAvailable->begin(), itAvailable->end(), inserter(intersecAv, intersecAv.begin()));

    CombinationStatus auxStatus;
    bool continueComb = comb->combine(intersecAv, this, auxStatus);     //! disparo el combinador

    if (auxStatus.status == Error)       //! Si no genero combinacion
    {
        if (!isMandatory)                //! Si no es mandatorio
        {
            continueComb = nextObs->combine_obs(lastComb, itAvailable);  //! comunico la combinacion al siguiente obs
        }
        else
        {
            subCombStatus = auxStatus;
            continueComb = false;
        }
    }
    itAvailable = old_itAvailable;       // Restablesco el valor
    lastComb = old_lastComb;             // Restablesco el valor
    return continueComb;
}

//! Observador intermedio
bool ParallelCombinatory::AuxObserverImp::update(const Avs* t)
{
    Avs unionAv;
    set_union(lastComb->begin(), lastComb->end(), t->begin(), t->end(), inserter(unionAv, unionAv.begin()));
    return nextObs->combine_obs(&unionAv, itAvailable);    //! comunico la combinacion al siguiente obs
}

bool ParallelCombinatory::FinalObserver::combine_obs(const Avs* combination, const Avs* /*itemsAvailable*/)
{
    bool continueObs(true);
    if (observed_by != NULL)
    {
        if (!combination->empty())
            continueObs = observed_by->update(combination);
        else
            subCombStatus.status = Error;
        subCombStatus.name = "parallel";
    }
    else
    {
        throw SystemError("at Parallel Combinatory: the observer is not set");
    }
    return continueObs;
}

//! Agrega una politica combinatoria
void ParallelCombinatory::add_policy(CombinatoryPolicy* policy, const Avs& reference_elements, bool isMandatory)
{
    AuxObserverImp* const aux = new AuxObserverImp(policy, reference_elements, isMandatory, subCombStatus);  //! observador auxiliar
    if (firstObs != NULL)                      //! si no es el primero
        lastObs->set_next_obs(aux);
    else
        firstObs = aux;
    lastObs = aux;
}

//! Combina en paralelo
bool ParallelCombinatory::combine(const Avs& elements, CombinationObserver* observer, CombinationStatus& status)
{
    CombinationObserver* old_lastObs = finalObs->get_final_obs(); // genero una copia en el stack
    finalObs->set_final_obs(observer);

    lastObs->set_next_obs(finalObs);
    Avs emptyComb;

    bool continueComb = firstObs->combine_obs(&emptyComb, &elements);           //! combino
    status = subCombStatus;

    finalObs->set_final_obs(old_lastObs);
    return continueComb;
}

